package jexxus.server;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;

import jexxus.common.Connection;
import jexxus.common.ConnectionListener;
import jexxus.common.Delivery;

/**
 * Represents a server's connection to a client.
 */
public class ConnectionToClient
        extends Connection
{
    private final Socket       mSocket;
    private final OutputStream mTcpOutput;
    private final InputStream  mTcpInput;

    private IServerForClients  mServerForClients;
    private boolean            mConnected = true;
    private int                mUdpPort   = -1;

    ConnectionToClient(final IServerForClients pServerForClients, final ConnectionListener pServerListener, final Socket pSocket)
            throws IOException
    {
        super(pServerListener, pSocket.getInetAddress().getHostAddress());

        mServerForClients = pServerForClients;
        mSocket = pSocket;
        mTcpOutput = new BufferedOutputStream(pSocket.getOutputStream());
        mTcpInput = new BufferedInputStream(pSocket.getInputStream());

        startTCPListener();
    }

    public void setServerForClients( final IServerForClients pServerForClients )
    {
        mServerForClients = pServerForClients;
    }

    private void startTCPListener()
    {
        Thread t = new Thread(new Runnable()
        {
            public void run()
            {
                while( true )
                {
                    byte[] ret;
                    try
                    {
                        ret = readTCP();
                    }
                    catch( SocketException e )
                    {
                        if( mConnected )
                        {
                            mConnected = false;
                            mServerForClients.connectionDied(ConnectionToClient.this, false);
                        }
                        else
                        {
                            mServerForClients.connectionDied(ConnectionToClient.this, true);
                        }
                        break;
                    }
                    catch( Exception e )
                    {
                        e.printStackTrace();
                        break;
                    }
                    if( ret == null )
                    {
                        // the stream has ended
                        if( mConnected )
                        {
                            mConnected = false;
                            mServerForClients.connectionDied(ConnectionToClient.this, false);
                        }
                        else
                        {
                            mServerForClients.connectionDied(ConnectionToClient.this, true);
                        }
                        break;
                    }
                    try
                    {
                        listener.receive(ret, ConnectionToClient.this);
                    }
                    catch( Exception e )
                    {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.setName("Jexxus-TCPSocketListener");
        t.start();
    }

    @Override
    public synchronized void send( byte[] pDataToBeSent, Delivery pDeliveryType )
    {
        if( mConnected == false )
        {
            throw new RuntimeException("Cannot send message when not connected!");
        }
        if( pDeliveryType == Delivery.TCP )
        {
            try
            {
                sendTCP(pDataToBeSent);
            }
            catch( IOException e )
            {
                System.err.println("Error writing TCP data.");
                System.err.println(e.toString());
            }
        }
        else if( pDeliveryType == Delivery.UDP )
        {
            mServerForClients.sendUDP(pDataToBeSent, this);
        }
    }

    /**
     * Closes this connection to the client.
     */
    public void exit()
    {
        mConnected = false;
        try
        {
            mTcpInput.close();
        }
        catch( IOException e )
        {
            e.printStackTrace();
        }
        try
        {
            mTcpOutput.close();
        }
        catch( IOException e )
        {
            e.printStackTrace();
        }
        try
        {
            mSocket.close();
        }
        catch( IOException e )
        {
            e.printStackTrace();
        }
    }

    InetAddress getAddress()
    {
        return mSocket.getInetAddress();
    }

    int getUDPPort()
    {
        return mUdpPort;
    }

    void setUDPPort( int port )
    {
        this.mUdpPort = port;
    }

    @Override
    public void close()
    {
        if( !mConnected )
        {
            throw new RuntimeException("Cannot close the connection when it is not connected.");
        }
        else
        {
            try
            {
                mSocket.close();
                mTcpInput.close();
                mTcpOutput.close();
            }
            catch( IOException e )
            {
                e.printStackTrace();
            }
            mConnected = false;
        }
    }

    @Override
    public boolean isConnected()
    {
        return mConnected;
    }

    @Override
    protected InputStream getTCPInputStream()
    {
        return mTcpInput;
    }

    @Override
    protected OutputStream getTCPOutputStream()
    {
        return mTcpOutput;
    }
}
